﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Deployment.WindowsInstaller.Package;
using Red.Core.Extensions;

namespace Red.Launcher.Utilities
{
    public class MsiExtractor : IDisposable
    {
        public class Exception : System.Exception
        {
            #region Public Types

            public enum Type
            {
                [Description( "Windows has run out of temporary files. Clean your temp directory and try again" )]
                TempFileName,

                [Description( "Could not find the installer" )]
                SourceMissing,

                [Description( "You do not have the required permissions to copy the installer" )]
                SourceRestricted,
            }

            #endregion Public Types

            #region Public Properties

            public Type Reason { get; private set; }

            #endregion Public Properties

            #region C-Tor

            public Exception( Type reason ): base( reason.GetDescription() )
            {
                Reason = reason;
            }

            public Exception( Type reason, System.Exception inner ) : base( reason.GetDescription(), inner )
            {
                Reason = reason;
            }

            #endregion C-Tor
        }

        #region Private Data

        int _filesExtracted;

        #endregion Private Data

        #region Public Properties

        public InstallPackage InstallPackage { get; private set; }
        public string InstallerPath { get; private set; }
        public List<string> DirectoryKeysToRemoveFromPath { get; private set; } = new List<string> { "CompanyFolder", "INSTALLFOLDER" };

        #endregion Public Properties

        #region C-Tor

        public MsiExtractor() {}

        #endregion C-Tor

        #region Public Methods

        public void Initialize( string installerPath )
        {
            try
            {
                InstallerPath = Path.GetTempFileName();
            }
            catch ( IOException e )
            {
                throw new Exception( Exception.Type.TempFileName, e );
            }

            try
            {
                File.Copy( installerPath, InstallerPath, true );
            }
            catch ( UnauthorizedAccessException e )
            {
                throw new Exception( Exception.Type.SourceRestricted, e );
            }
            catch ( IOException e )
            {
                throw new Exception( Exception.Type.SourceMissing, e );
            }

            InstallPackage = new InstallPackage( InstallerPath, DatabaseOpenMode.Transact );
        }

        public void Extract( string targetPath )
        {
            FixPaths();

            _filesExtracted = 0;

            InstallPackage.WorkingDirectory = targetPath;
            InstallPackage.Message += MessageHandler;
            InstallPackage.ExtractFiles();
            InstallPackage.Message -= MessageHandler;

            DirectoryInfo destination = new DirectoryInfo( Path.Combine( targetPath, "WITEMP" ) );
            destination.Delete( true );
        }

        #endregion Public Methods

        #region Private Methods

        private void FixPaths()
        {
            foreach ( string key in DirectoryKeysToRemoveFromPath )
            {
                var companyFolderRecord = new Record(".", key);
                InstallPackage.Execute( "UPDATE `Directory` SET `DefaultDir` = ? WHERE `Directory` = ?", companyFolderRecord );
            }

            InstallPackage.UpdateDirectories();
        }

        private void MessageHandler( string format, params object[] args )
        {
            Debug.Print( format, args );

            if ( args.Length == 3 )
            {
                ++_filesExtracted;
                Progress?.Invoke( _filesExtracted, InstallPackage.Files.Count, (string)args[ 2 ] );
            }
        }

        #endregion Private Methods

        #region Events

        public delegate void ProgressDelegate( int filesExtracted, int totalFiles, string currentFile );
        public event ProgressDelegate Progress;

        #endregion Events

        #region IDisposable

        public bool IsDisposed { get; private set; }

        public void Dispose()
        {
            Dispose( true );
            GC.SuppressFinalize( this );
        }

        protected virtual void Dispose( bool disposing )
        {
            if ( !IsDisposed )
            {
                if ( disposing )
                {
                    // Free managed resources here
                    InstallPackage?.Dispose();
                }

                // Always free unmanaged resources here
                try
                {
                    File.Delete( InstallerPath );
                }
                catch ( UnauthorizedAccessException e )
                {
                    Debug.Print( "Could not delete temporary installer '{0}'\nThis is probably because you're not using MsiExtractor in a using() statement and so C# decided to dispose of the objects in an arbitrary order\n{1}", InstallerPath, e );
                }

                IsDisposed = true;
            }
        }

        #endregion IDisposable
    }
}
